1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This file contains an analyzer that uses clang-tidy. 7 */ 8 module code_checker.engine.builtin.clang_tidy; 9 10 import std.typecons : Tuple; 11 import std.exception : collectException; 12 import std.concurrency : Tid, thisTid; 13 import logger = std.experimental.logger; 14 15 import code_checker.engine.builtin.clang_tidy_classification : CountErrorsResult; 16 import code_checker.engine.types; 17 import code_checker.process : RunResult; 18 import code_checker.types; 19 20 @safe: 21 22 class ClangTidy : BaseFixture { 23 private { 24 Environment env; 25 Result result_; 26 string[] tidyArgs; 27 } 28 29 override string explain() { 30 return "using clang-tidy"; 31 } 32 33 /// The environment the analyzers execute in. 34 override void putEnv(Environment v) { 35 this.env = v; 36 } 37 38 /// Setup the environment for analyze. 39 override void setup() { 40 import std.algorithm; 41 import std.array : appender, array; 42 import std.ascii; 43 import std.file : exists; 44 import std.range : put; 45 import std.format : format; 46 import code_checker.engine.builtin.clang_tidy_classification : filterSeverity; 47 48 auto app = appender!(string[])(); 49 app.put(env.clangTidy.binary); 50 51 app.put("-p=."); 52 53 if (env.clangTidy.applyFixit) { 54 app.put(["-fix"]); 55 } else if (env.clangTidy.applyFixitErrors) { 56 app.put(["-fix-errors"]); 57 } else { 58 app.put("-warnings-as-errors=*"); 59 } 60 61 env.compiler.extraFlags.map!(a => ["-extra-arg", a]).joiner.copy(app); 62 63 ["-header-filter", env.clangTidy.headerFilter].copy(app); 64 65 // inactivate those that are below the configured severity level. 66 // dfmt off 67 env.clangTidy.checks ~= 68 filterSeverity!(a => a < env.staticCode.severity) 69 .map!(a => format("-%s", a)) 70 .array; 71 // dfmt on 72 73 if (exists(ClangTidyConstants.confFile)) { 74 logger.infof("Using clang-tidy settings from the local '%s'", 75 ClangTidyConstants.confFile); 76 } else { 77 logger.trace("Using config from the TOML file"); 78 79 auto c = appender!string(); 80 c.put(`{Checks: "`); 81 env.clangTidy.checks.joiner(",").copy(c); 82 c.put(`",`); 83 c.put("CheckOptions: ["); 84 env.clangTidy.options.joiner(",").copy(c); 85 c.put("]"); 86 c.put("}"); 87 88 app.put("-config"); 89 app.put(c.data); 90 } 91 92 tidyArgs = app.data; 93 } 94 95 /// Execute the analyzer. 96 override void execute() { 97 if (env.clangTidy.applyFixit || env.clangTidy.applyFixitErrors) { 98 executeFixit(env, tidyArgs, result_); 99 } else { 100 executeParallel(env, tidyArgs, result_); 101 } 102 } 103 104 /// Cleanup after analyze. 105 override void tearDown() { 106 } 107 108 /// Returns: the result of the analyzer. 109 override Result result() { 110 return result_; 111 } 112 } 113 114 void executeParallel(Environment env, string[] tidyArgs, ref Result result_) { 115 import core.time : dur; 116 import std.concurrency : Tid, thisTid, receiveTimeout; 117 import std.format : format; 118 import std.parallelism : task, TaskPool; 119 import code_checker.compile_db : UserFileRange, parseFlag, 120 CompileCommandFilter, SearchResult; 121 import code_checker.engine.logger : Logger; 122 123 bool logged_failure; 124 auto logg = Logger(env.logg.dir); 125 126 void handleResult(immutable(TidyResult)* res_) @trusted nothrow { 127 import std.array : appender; 128 import std.format : format; 129 import std.typecons : nullableRef; 130 import colorize : Color, color, Background, Mode; 131 import code_checker.engine.builtin.clang_tidy_classification : mapClangTidy; 132 133 auto res = nullableRef(cast() res_); 134 135 logger.infof("%s '%s'", "clang-tidy analyzing".color(Color.yellow, 136 Background.black), res.file).collectException; 137 138 result_.score += res.clangTidyStatus == 0 ? 1 : res.errors.score; 139 140 if (res.clangTidyStatus != 0) { 141 res.print; 142 143 if (env.logg.toFile) { 144 try { 145 logg.put(res.file, [res.output]); 146 } catch (Exception e) { 147 logger.warning(e.msg).collectException; 148 logger.warning("Unable to log to file").collectException; 149 } 150 } 151 152 if (!logged_failure) { 153 result_.msg ~= Msg(MsgSeverity.failReason, "clang-tidy warn about file(s)"); 154 logged_failure = true; 155 } 156 157 try { 158 result_.msg ~= Msg(MsgSeverity.improveSuggestion, 159 format("clang-tidy: %-(%s, %) in %s", res.errors.toRange, res.file)); 160 } catch (Exception e) { 161 logger.warning(e.msg).collectException; 162 logger.warning("Unable to add user message to the result").collectException; 163 } 164 } 165 166 result_.status = mergeStatus(result_.status, res.clangTidyStatus == 0 167 ? Status.passed : Status.failed); 168 logger.trace(result_).collectException; 169 } 170 171 auto pool = new TaskPool; 172 scope (exit) 173 pool.finish; 174 175 static struct DoneCondition { 176 int expected; 177 int replies; 178 179 bool isWaitingForReplies() { 180 return replies < expected; 181 } 182 } 183 184 DoneCondition cond; 185 186 foreach (cmd; UserFileRange(env.compileDb, env.files, env.compiler.extraFlags, env.flagFilter)) { 187 if (cmd.isNull) { 188 result_.status = Status.failed; 189 result_.score -= 100; 190 result_.msg ~= Msg(MsgSeverity.failReason, 191 "clang-tidy where unable to find one of the specified files in compile_commands.json"); 192 break; 193 } 194 195 cond.expected++; 196 197 immutable(TidyWork)* w = () @trusted{ 198 return cast(immutable) new TidyWork(tidyArgs, cmd.absoluteFile, !env.logg.toFile); 199 }(); 200 auto t = task!taskTidy(thisTid, w); 201 pool.put(t); 202 } 203 204 while (cond.isWaitingForReplies) { 205 () @trusted{ 206 try { 207 if (receiveTimeout(1.dur!"seconds", &handleResult)) { 208 cond.replies++; 209 } 210 } catch (Exception e) { 211 logger.error(e.msg); 212 } 213 }(); 214 } 215 } 216 217 /// Run clang-tidy with to fix the code. 218 void executeFixit(Environment env, string[] tidyArgs, ref Result result_) { 219 import std.algorithm : copy, map; 220 import std.array : array; 221 import std.path : buildPath; 222 import std.process : spawnProcess, wait; 223 import code_checker.compile_db : UserFileRange, CompileCommandFilter; 224 import code_checker.engine.logger : Logger; 225 226 AbsolutePath[] files; 227 auto logg = Logger(env.logg.dir); 228 229 if (env.logg.toFile) { 230 logg.setup; 231 tidyArgs ~= ["-export-fixes", buildPath(env.logg.dir, "fixes.yaml")]; 232 } 233 234 foreach (cmd; UserFileRange(env.compileDb, env.files, null, CompileCommandFilter.init)) { 235 if (cmd.isNull) { 236 result_.status = Status.failed; 237 result_.score -= 100; 238 result_.msg ~= Msg(MsgSeverity.failReason, 239 "clang-tidy where unable to find one of the specified files in compile_commands.json"); 240 break; 241 } 242 files ~= cmd.absoluteFile; 243 } 244 245 auto args = tidyArgs ~ files.map!(a => cast(string) a).array; 246 logger.tracef("run: %s", args); 247 248 auto status = spawnProcess(args).wait; 249 if (status != 0) { 250 result_.status = Status.failed; 251 result_.score -= 1000; 252 result_.msg ~= Msg(MsgSeverity.failReason, "clang-tidy failed to apply fixes"); 253 } 254 } 255 256 struct TidyResult { 257 AbsolutePath file; 258 CountErrorsResult errors; 259 260 /// Exit status from running clang tidy 261 int clangTidyStatus; 262 263 /// Output to the user 264 string[] output; 265 266 void print() @safe nothrow const scope { 267 import std.ascii : newline; 268 import std.stdio : writeln; 269 270 foreach (l; output) 271 writeln(l).collectException; 272 } 273 } 274 275 struct TidyWork { 276 string[] args; 277 AbsolutePath p; 278 bool useColors; 279 } 280 281 void taskTidy(Tid owner, immutable TidyWork* work_) nothrow @trusted { 282 import std.algorithm : copy; 283 import std.array : appender; 284 import std.concurrency : send; 285 import std.format : format; 286 import code_checker.engine.builtin.clang_tidy_classification : mapClangTidy, 287 Severity, color; 288 289 auto tres = new TidyResult; 290 TidyWork* work = cast(TidyWork*) work_; 291 292 try { 293 string diagMsg(Severity s, string diag) { 294 tres.errors.put(s); 295 if (work.useColors) 296 return format("%s[%s]", diag, color(s)); 297 return format("%s[%s]", diag, s); 298 } 299 300 tres.file = work.p; 301 302 auto res = runClangTidy(work.args, [work.p]); 303 tres.clangTidyStatus = res.status; 304 305 auto app = appender!(string[])(); 306 mapClangTidy!diagMsg(res.stdout, app); 307 308 res.stderr.copy(app); 309 310 tres.output = app.data; 311 } catch (Exception e) { 312 logger.warning(e.msg).collectException; 313 } 314 315 while (true) { 316 try { 317 owner.send(cast(immutable) tres); 318 break; 319 } catch (Exception e) { 320 logger.tracef("failed sending to: %s", owner).collectException; 321 } 322 } 323 } 324 325 struct ClangTidyConstants { 326 static immutable confFile = ".clang-tidy"; 327 } 328 329 auto runClangTidy(string[] tidy_args, AbsolutePath[] fname) { 330 import std.algorithm : copy; 331 import std.array : appender; 332 import code_checker.process; 333 334 auto app = appender!(string[])(); 335 tidy_args.copy(app); 336 fname.copy(app); 337 338 return run(app.data); 339 }